#include "Shader.h"

#include <stdlib.h>
#include <stdio.h>
#include <sys/stat.h>
#include <string>

//Read shader file
char* readShaderFile(const char *filename) 
{
  FILE *file;
  struct stat st;
  
  file = fopen(filename, "r");
  
  if(file == NULL)
  {
    fprintf( stderr, "ERROR: Cannot open shader file!");
    return 0;
  }
  
  stat(filename,&st);
  
  int bytesinfile = st.st_size;
  char *buffer = (char*)malloc(bytesinfile+sizeof(char));
  int bytesread = fread( buffer, 1, bytesinfile, file);
  buffer[bytesread] = 0; // Terminate the string with 0
  fclose(file);
  
  return buffer;
}

//Vertex, Fragment Shader
Shader::Shader(std::string vsFile, std::string fsFile)
{
	init(vsFile, fsFile);
}

//Vertex, Fragment, Geometry Shader
Shader::Shader(std::string vsFile, std::string fsFile, std::string gsFile)
{
	init(vsFile, fsFile, gsFile);
}

//Vertex, Fragment, Geometry, Tessellation Control, Tessellation Evaluation Shader
Shader::Shader(std::string vsFile, std::string fsFile, std::string gsFile, std::string TessControlFile, std::string TessEvalFile)
{
	init(vsFile, fsFile, gsFile, TessControlFile, TessEvalFile);
}

Shader::~Shader()
{
	glDetachShader(shaderId, shaderVp);
	glDetachShader(shaderId, shaderFp);
	glDetachShader(shaderId, shaderGp);

	glDeleteShader(shaderGp);
	glDeleteShader(shaderFp);
	glDeleteShader(shaderVp);
	glDeleteShader(shaderId);
}

//Vertex, Fragment Shader
void Shader::init(std::string vsFile, std::string fsFile)
{
	GLint isCompiled;
	GLint isLinked;

	////////Vertex shader////////
	shaderVp = glCreateShader(GL_VERTEX_SHADER);
  
	const char *vertexAssembly = readShaderFile(vsFile.c_str());	
	glShaderSource( shaderVp, 1, &vertexAssembly, NULL );
	glCompileShader( shaderVp);
	free((void *)vertexAssembly);
  
	//Check for compile errors of the vertex shader
	glGetShaderiv( shaderVp, GL_COMPILE_STATUS, &isCompiled );
	if(isCompiled == GL_FALSE)
	{
		char str[256];
		glGetShaderInfoLog(shaderVp, 256, NULL, str);
		fprintf( stderr, "Vertex shader compile error: %s\n", str);
	}

	///////Fragment shader////////
	shaderFp = glCreateShader( GL_FRAGMENT_SHADER );
  
	const char *fragmentAssembly = readShaderFile(fsFile.c_str());
	glShaderSource( shaderFp, 1, &fragmentAssembly, NULL );
	glCompileShader( shaderFp );
	free((void *)fragmentAssembly);
  
	//Check for compile errors of the fragment shader
	glGetShaderiv( shaderFp, GL_COMPILE_STATUS, &isCompiled );
	if(isCompiled == GL_FALSE)
	{
		char str[256];
		glGetShaderInfoLog(shaderFp, 256, NULL, str);
		fprintf( stderr, "Fragment shader compile error: %s\n", str );
	}

	//////Program object//////
	shaderId = glCreateProgram();
	glAttachShader( shaderId, shaderVp );
	glAttachShader( shaderId, shaderFp );

	//glBindAttribLocation(shaderId, 0, "in_Position"); 
	//glBindAttribLocation(shaderId, 1, "in_Color"); 

	glLinkProgram(shaderId);

	//Check for program errors
	glGetProgramiv( shaderId, GL_LINK_STATUS, &isLinked );
  	if( isLinked == GL_FALSE )
	{
		char str[256];
		glGetProgramInfoLog( shaderId, 256, NULL, str );
		fprintf( stderr, "Program object linking error: %s\n", str );
	}
}

//Vertex, Fragment, Geometry Shader
void Shader::init(std::string vsFile, std::string fsFile, std::string gsFile)
{
	GLint isCompiled;
	GLint isLinked;

	////////Vertex shader////////
	shaderVp = glCreateShader(GL_VERTEX_SHADER);
  
	const char *vertexAssembly = readShaderFile(vsFile.c_str());	
	glShaderSource( shaderVp, 1, &vertexAssembly, NULL );
	glCompileShader( shaderVp);
	free((void *)vertexAssembly);
  
	//Check for compile errors of the vertex shader
	glGetShaderiv( shaderVp, GL_COMPILE_STATUS, &isCompiled );
	if(isCompiled == GL_FALSE)
	{
		char str[256];
		glGetShaderInfoLog(shaderVp, 256, NULL, str);
		fprintf( stderr, "Vertex shader compile error: %s\n", str);
	}

	///////Fragment shader////////
	shaderFp = glCreateShader( GL_FRAGMENT_SHADER );
  
	const char *fragmentAssembly = readShaderFile(fsFile.c_str());
	glShaderSource( shaderFp, 1, &fragmentAssembly, NULL );
	glCompileShader( shaderFp );
	free((void *)fragmentAssembly);
  
	//Check for compile errors of the fragment shader
	glGetShaderiv( shaderFp, GL_COMPILE_STATUS, &isCompiled );
	if(isCompiled == GL_FALSE)
	{
		char str[256];
		glGetShaderInfoLog(shaderFp, 256, NULL, str);
		fprintf( stderr, "Fragment shader compile error: %s\n", str );
	}

	///////Geometry shader////////
	shaderGp = glCreateShader( GL_GEOMETRY_SHADER );
  
	const char *GeometryAssembly = readShaderFile(gsFile.c_str());
	glShaderSource( shaderGp, 1, &GeometryAssembly, NULL );
	glCompileShader( shaderGp );
	free((void *)GeometryAssembly);
  
	//Check for compile errors of the geometry shader
	glGetShaderiv( shaderGp, GL_COMPILE_STATUS, &isCompiled );
	if(isCompiled == GL_FALSE)
	{
		char str[256];
		glGetShaderInfoLog(shaderGp, 256, NULL, str);
		fprintf( stderr, "Fragment shader compile error: %s\n", str );
	}

	//////Program object//////
	shaderId = glCreateProgram();
	glAttachShader( shaderId, shaderVp );
	glAttachShader( shaderId, shaderFp );
	glAttachShader( shaderId, shaderGp );

	//glBindAttribLocation(shaderId, 0, "in_Position"); 
	//glBindAttribLocation(shaderId, 1, "in_Color"); 

	glLinkProgram(shaderId);

	//Check for program errors
	glGetProgramiv( shaderId, GL_LINK_STATUS, &isLinked );
  	if( isLinked == GL_FALSE )
	{
		char str[256];
		glGetProgramInfoLog( shaderId, 256, NULL, str );
		fprintf( stderr, "Program object linking error: %s\n", str );
	}
}

//Vertex, Fragment, Geometry Tessellation Control, Tessellation Evaluation Shader
void Shader::init(std::string vsFile, std::string fsFile, std::string gsFile, std::string TessControlFile, std::string TessEvalFile)
{
	GLint isCompiled;
	GLint isLinked;

	////////Vertex shader////////
	shaderVp = glCreateShader(GL_VERTEX_SHADER);
  
	const char *vertexAssembly = readShaderFile(vsFile.c_str());	
	glShaderSource( shaderVp, 1, &vertexAssembly, NULL );
	glCompileShader( shaderVp);
	free((void *)vertexAssembly);
  
	//Check for compile errors of the vertex shader
	glGetShaderiv( shaderVp, GL_COMPILE_STATUS, &isCompiled );
	if(isCompiled == GL_FALSE)
	{
		char str[256];
		glGetShaderInfoLog(shaderVp, 256, NULL, str);
		fprintf( stderr, "Vertex shader compile error: %s\n", str);
	}

	///////Fragment shader////////
	shaderFp = glCreateShader( GL_FRAGMENT_SHADER );
  
	const char *fragmentAssembly = readShaderFile(fsFile.c_str());
	glShaderSource( shaderFp, 1, &fragmentAssembly, NULL );
	glCompileShader( shaderFp );
	free((void *)fragmentAssembly);
  
	//Check for compile errors of the fragment shader
	glGetShaderiv( shaderFp, GL_COMPILE_STATUS, &isCompiled );
	if(isCompiled == GL_FALSE)
	{
		char str[256];
		glGetShaderInfoLog(shaderFp, 256, NULL, str);
		fprintf( stderr, "Fragment shader compile error: %s\n", str );
	}

	///////Geometry shader////////
	shaderGp = glCreateShader( GL_GEOMETRY_SHADER );
  
	const char *GeometryAssembly = readShaderFile(gsFile.c_str());
	glShaderSource( shaderGp, 1, &GeometryAssembly, NULL );
	glCompileShader( shaderGp );
	free((void *)GeometryAssembly);
  
	//Check for compile errors of the geometry shader
	glGetShaderiv( shaderGp, GL_COMPILE_STATUS, &isCompiled );
	if(isCompiled == GL_FALSE)
	{
		char str[256];
		glGetShaderInfoLog(shaderGp, 256, NULL, str);
		fprintf( stderr, "Geometry shader compile error: %s\n", str );
	}

	///////Tessellation Control shader////////
	shaderTessCp = glCreateShader( GL_TESS_CONTROL_SHADER );
  
	const char *TessControlAssembly = readShaderFile(TessControlFile.c_str());
	glShaderSource( shaderTessCp, 1, &TessControlAssembly, NULL );
	glCompileShader( shaderTessCp );
	free((void *)TessControlAssembly);
  
	//Check for compile errors of the geometry shader
	glGetShaderiv( shaderTessCp, GL_COMPILE_STATUS, &isCompiled );
	if(isCompiled == GL_FALSE)
	{
		char str[256];
		glGetShaderInfoLog(shaderTessCp, 256, NULL, str);
		fprintf( stderr, "Tessellation Control shader compile error: %s\n", str );
	}

	///////Tessellation Evaluation shader////////
	shaderTessEp = glCreateShader( GL_TESS_EVALUATION_SHADER );
  
	const char *TessEvalAssembly = readShaderFile(TessEvalFile.c_str());
	glShaderSource( shaderTessEp, 1, &TessEvalAssembly, NULL );
	glCompileShader( shaderTessEp );
	free((void *)TessEvalAssembly);
  
	//Check for compile errors of the geometry shader
	glGetShaderiv( shaderTessEp, GL_COMPILE_STATUS, &isCompiled );
	if(isCompiled == GL_FALSE)
	{
		char str[256];
		glGetShaderInfoLog(shaderTessEp, 256, NULL, str);
		fprintf( stderr, "Tessellation Evaluation shader compile error: %s\n", str );
	}

	//////Program object//////
	shaderId = glCreateProgram();
	glAttachShader( shaderId, shaderVp );
	glAttachShader( shaderId, shaderFp );
	glAttachShader( shaderId, shaderGp );
	glAttachShader( shaderId, shaderTessCp );
	glAttachShader( shaderId, shaderTessEp );

	//glBindAttribLocation(shaderId, 0, "in_Position"); 
	//glBindAttribLocation(shaderId, 1, "in_Color"); 

	glLinkProgram(shaderId);

	//Check for program errors
	glGetProgramiv( shaderId, GL_LINK_STATUS, &isLinked );
  	if( isLinked == GL_FALSE )
	{
		char str[256];
		glGetProgramInfoLog( shaderId, 256, NULL, str );
		fprintf( stderr, "Program object linking error: %s\n", str );
	}
}


void Shader::bind()
{
	glUseProgram(shaderId);
}

void Shader::unbind()
{
	glUseProgram(0);
}

unsigned int Shader::id()
{
	return shaderId;
}

